/*
 * Copyright 2024 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.pajamas.starter.core.repo;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import javax.annotation.Nonnull;
import javax.validation.constraints.NotBlank;

import lombok.extern.slf4j.Slf4j;

/**
 * @author william
 * @since 2023/12/30
 */
@Slf4j
public final class PageableBuilder implements Serializable {
    private static final long serialVersionUID = 4707641975310787380L;
    private final List<Sort.Order> orders;
    private Integer page, size;

    private PageableBuilder() {
        this.orders = new ArrayList<>();
    }

    @Nonnull
    public static PageableBuilder builder() {
        return new PageableBuilder();
    }

    public PageableBuilder paging(int page, int size) {
        this.page = page;
        this.size = size;
        return this;
    }

    public PageableBuilder paging(@Nonnull Pageable pageable) {
        Objects.requireNonNull(pageable);
        if (pageable.isUnpaged()) {
            return this;
        }
        return paging(pageable.getPageNumber(), pageable.getPageSize());
    }

    public PageableBuilder sorting(@Nonnull Sort.Direction direction, @Nonnull String property,
                                   @Nonnull Sort.NullHandling nullHandling, boolean ignoreCase) {
        Sort.Order order = new Sort.Order(direction, property, nullHandling);
        this.orders.add(ignoreCase ? order.ignoreCase() : order);
        return this;
    }

    public PageableBuilder sorting(@Nonnull Sort.Direction direction, @NotBlank String property,
                                   @Nonnull Sort.NullHandling nullHandling) {
        return this.sorting(direction, property, nullHandling, Boolean.FALSE);
    }

    public PageableBuilder sorting(@Nonnull Sort.Direction direction, @NotBlank String property) {
        return this.sorting(direction, property, Sort.NullHandling.NATIVE, Boolean.FALSE);
    }

    public PageableBuilder sorting(@NotBlank String property) {
        return this.sorting(Sort.Direction.ASC, property);
    }

    public PageableBuilder sorting(@Nonnull Sort sort) {
        Objects.requireNonNull(sort);
        if (sort.isUnsorted()) {
            return this;
        }
        List<Sort.Order> orders = sort.get().collect(Collectors.toList());
        if (CollectionUtils.isNotEmpty(orders)) {
            this.orders.addAll(orders);
        }
        return this;
    }

    public PageableBuilder inheritFrom(@Nonnull Pageable pageable) {
        Objects.requireNonNull(pageable);
        return this.paging(pageable).sorting(pageable.getSort());
    }

    @Nonnull
    public Pageable build() {
        log.info("building pageable args, page:[{}], size:[{}], orders:[{}]", this.page, this.size, this.orders);

        Sort sort;
        if (CollectionUtils.isNotEmpty(this.orders)) {
            sort = Sort.by(this.orders);
        } else {
            sort = Sort.unsorted();
        }

        if (!ObjectUtils.allNotNull(this.page, this.size)) {
            if (sort.isSorted()) {
                log.warn("building pageable lose sort, because its page or size exist null");
            }
            return Pageable.unpaged();
        }
        return PageRequest.of(this.page, this.size, sort);
    }
}
